package MyGui;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.table.*;
import java.text.*;
import java.util.*;

import DistSys.*;
import Element.*;
import Element.Geom.*;

/**
 * The class <code>CostSumDlg</code> implements the cost summary dialog.
 *
 */
public class CostSumDlg extends MyDlgFrame implements ActionListener {
    /**
      * Creates a <code>CostSumDlg</code>.
      */
    public CostSumDlg(MainFrame parent, String title) {
        super(parent, title);
        setSize(540,450);
        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        // Initialize components
        tfDescription = new JTextField("[Description]", 15);
        JLabel labelDescription = new JLabel("Description:");
        labelDescription.setDisplayedMnemonic('D');
        labelDescription.setLabelFor(tfDescription);
        comboArea = new ZoneComboBox();
        JLabel labelArea = new JLabel("Area:");
        labelArea.setDisplayedMnemonic('R');
        labelArea.setLabelFor(comboArea);
        tfDiscountRate = new JTextField("15", 3);
        JLabel labelDiscountRate = new JLabel("Discount Rate [%]");
        labelDiscountRate.setDisplayedMnemonic('I');
        labelDiscountRate.setLabelFor(tfDiscountRate);
        tfProjectLife = new JTextField("20", 3);
        JLabel labelProjectLife = new JLabel("Project Life [yr]");
        labelProjectLife.setDisplayedMnemonic('J');
        labelProjectLife.setLabelFor(tfProjectLife);
        tfCostOfEnergy = new JTextField("0.05", 3);
        JLabel labelCostOfEnergy = new JLabel("Cost of Energy [$/kWh]");
        labelCostOfEnergy.setDisplayedMnemonic('O');
        labelCostOfEnergy.setLabelFor(tfCostOfEnergy);
        tfLossFactor = new JTextField("0.4", 3);
        JLabel labelLossFactor = new JLabel("Loss Factor");
        labelLossFactor.setDisplayedMnemonic('F');
        labelLossFactor.setLabelFor(tfLossFactor);
        btnAddToList = new JButton("Add to List");
        btnAddToList.setMnemonic('A');
        btnAddToList.addActionListener(this);
        for (int i = 0; i < CASE_COUNT; i++) {
            comboCase[i] = new JComboBox();
            comboCase[i].setEditable(false);
            comboCase[i].addActionListener(this);
        }
        modelCaseResult = new CaseResultModel();
        tblCaseResult = new JTable(modelCaseResult);
        tblCaseResult.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        tblCaseResult.getColumnModel().getColumn(0).setPreferredWidth(170);
        for (int i = 0; i < CASE_COUNT; i++) {
            tblCaseResult.getColumnModel().getColumn(2*i + 1).setPreferredWidth(68);
            tblCaseResult.getColumnModel().getColumn(2*i + 2).setPreferredWidth(90);
        }
        tblCaseResult.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        tblCaseResult.setShowGrid(false);
        tblCaseResult.getTableHeader().setReorderingAllowed(false);

        btnClearEntry = new JButton("Clear Left Entry");
        btnClearEntry.setMnemonic('C');
        btnClearEntry.setEnabled(false);
        btnClearEntry.addActionListener(this);
        btnClearList = new JButton("Clear List");
        btnClearList.setMnemonic('L');
        btnClearList.setEnabled(false);
        btnClearList.addActionListener(this);
        btnExport = new JButton("Export to Excel");
        btnExport.setMnemonic('E');
        btnExport.setEnabled(false);
        btnExport.addActionListener(this);

        // Construct north panel
        Box nbox = Box.createVerticalBox();
        contentPane.add(nbox, BorderLayout.NORTH);

        JPanel nnpanel = new JPanel(gridBagLayout);
        nnpanel.setBorder(BorderFactory.createTitledBorder(
            BorderFactory.createEtchedBorder(), " Case "));
        nbox.add(nnpanel);

        add(nnpanel, labelDescription, 0, 0, 1, 1, 0, 0,
            GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
            0, GAP, 0, GAP);
        tfDescription.setNextFocusableComponent(comboArea);
        add(nnpanel, tfDescription, 0, 1, 1, 1, 100, 0,
            GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, GAP);

        add(nnpanel, labelArea, 0, 2, 1, 1, 0, 0,
            GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
            0, GAP, 0, GAP);
        comboArea.setNextFocusableComponent(tfDiscountRate);
        add(nnpanel, comboArea, 0, 3, 1, 1, 100, 0,
            GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
            0, GAP, GAP, GAP);

        tfDiscountRate.setNextFocusableComponent(tfProjectLife);
        add(nnpanel, tfDiscountRate, 1, 0, 1, 1, 25, 0,
            GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, 0);
        add(nnpanel, labelDiscountRate, 2, 0, 1, 1, 0, 0,
            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, GAP);

        tfProjectLife.setNextFocusableComponent(tfCostOfEnergy);
        add(nnpanel, tfProjectLife, 1, 1, 1, 1, 25, 0,
            GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, 0);
        add(nnpanel, labelProjectLife, 2, 1, 1, 1, 0, 0,
            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, GAP);

        tfCostOfEnergy.setNextFocusableComponent(tfLossFactor);
        add(nnpanel, tfCostOfEnergy, 1, 2, 1, 1, 25, 0,
            GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, 0);
        add(nnpanel, labelCostOfEnergy, 2, 2, 1, 1, 0, 0,
            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
            0, GAP, 0, GAP);

        tfLossFactor.setNextFocusableComponent(btnAddToList);
        add(nnpanel, tfLossFactor, 1, 3, 1, 1, 25, 0,
            GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL,
            0, GAP, GAP, 0);
        add(nnpanel, labelLossFactor, 2, 3, 1, 1, 0, 0,
            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
            0, GAP, GAP, GAP);

        JPanel nspanel = new JPanel();
        nbox.add(nspanel);
        nspanel.add(btnAddToList);

        // Construct center panel
        Box cbox = Box.createVerticalBox();
        contentPane.add(cbox, BorderLayout.CENTER);

        JPanel cnpanel = new JPanel(gridBagLayout);
        cnpanel.setBorder(BorderFactory.createTitledBorder(
          BorderFactory.createEtchedBorder(), " Results "));
        cbox.add(cnpanel);

        for (int i = 0; i < CASE_COUNT; i++) {
            add(cnpanel, comboCase[i], i, 0, 1, 1, 100, 0,
                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
                0, GAP, GAP, GAP);
        }
        comboCase[CASE_COUNT-1].setNextFocusableComponent(btnClearEntry);

        add(cnpanel, new JScrollPane(tblCaseResult),
            0, 1, CASE_COUNT, 1, 100, 100,
            GridBagConstraints.CENTER, GridBagConstraints.BOTH,
            0, GAP, GAP, GAP);

        JPanel cspanel = new JPanel();
        cbox.add(cspanel);
        cspanel.add(btnClearEntry);
        cspanel.add(btnClearList);
        btnExport.setNextFocusableComponent(tfDescription);
        cspanel.add(btnExport);
    }

    /**
     * This method is public as an implementation side effect.
     * do not call or override.
     */
    public void actionPerformed(ActionEvent evt) {
        Object source = evt.getSource();
        if (source == btnAddToList) {
            addToList();
        } else if (source == btnClearEntry) {
            int iIndex = comboCase[0].getSelectedIndex();
            if (iIndex >= 0) {
                bLockShowResults = true;
                Sys.costSumCases.removeElementAt(iIndex);
                for (int i = 0; i < CASE_COUNT; i++)
                    comboCase[i].removeItemAt(iIndex);
                bLockShowResults = false;
                ShowResults();
                if (Sys.costSumCases.size() == 0) {
                    btnClearEntry.setEnabled(false);
                    btnClearList.setEnabled(false);
                    btnExport.setEnabled(false);
                }
            }
        } else if (source == btnClearList) {
            bLockShowResults = true;
            Sys.costSumCases.removeAllElements();
            for (int i = 0; i < CASE_COUNT; i++)
                comboCase[i].removeAllItems();
            bLockShowResults = false;
            ShowResults();
            if (Sys.costSumCases.size() == 0) {
                btnClearEntry.setEnabled(false);
                btnClearList.setEnabled(false);
                btnExport.setEnabled(false);
            }
        } else if (source == btnExport) {
            ExportResults();
        } else {
            for (int i = 0; i < CASE_COUNT; i++) {
                if (source == comboCase[i]) {
                    ShowResults();
                    break;
                }
            }
        }
    }

    /*
     * Performs the cost summary and adds the result to the list.
     */
    private void addToList() {
        NumberFormat curr = NumberFormat.getNumberInstance();
        curr.setMaximumFractionDigits(0);
        NumberFormat num = NumberFormat.getNumberInstance();
        NumberFormat numUnit = NumberFormat.getNumberInstance();
        numUnit.setMinimumFractionDigits(SysConst.unitFracDigits[Sys.prjInfo.unitOfLength]);
        numUnit.setMaximumFractionDigits(SysConst.unitFracDigits[Sys.prjInfo.unitOfLength]);
        CostSumCase c = null;
        boolean bNewCase = true;
        double[] dData = new double[CostSumCase.COUNT];
        double dZone;
        String sDescription = tfDescription.getText();

        // Search whether a case with this description is existing
        for (int i = 0; i < Sys.costSumCases.size(); i++) {
            c = (CostSumCase)Sys.costSumCases.get(i);
            if (sDescription.equals(c.asData[CostSumCase.DESCRIPTION])) {
                bNewCase = false;
                break;
            }
        }
        // If it doesn't create a new case, otherwise overwrite the old one
        if (bNewCase) {
            c = new CostSumCase();
            c.asData[CostSumCase.DESCRIPTION] = sDescription;
        }

        // Retrieve information to dialog items
        try {
            dData[CostSumCase.ZONE] = dZone = comboArea.getSelectedZone();
            c.asData[CostSumCase.ZONE] = Zone.getZoneString(dZone);
            dData[CostSumCase.DISCOUNT_RATE] = Double.parseDouble(tfDiscountRate.getText());
            c.asData[CostSumCase.DISCOUNT_RATE] = num.format(dData[CostSumCase.DISCOUNT_RATE]);
            dData[CostSumCase.PROJECT_LIFE] = Double.parseDouble(tfProjectLife.getText());
            c.asData[CostSumCase.PROJECT_LIFE] = num.format(dData[CostSumCase.PROJECT_LIFE]);
            dData[CostSumCase.LOSS_FACTOR] = Double.parseDouble(tfLossFactor.getText());
            c.asData[CostSumCase.LOSS_FACTOR] = num.format(dData[CostSumCase.LOSS_FACTOR]);
            dData[CostSumCase.COST_OF_ENERGY] = Double.parseDouble(tfCostOfEnergy.getText());
            c.asData[CostSumCase.COST_OF_ENERGY] = num.format(dData[CostSumCase.COST_OF_ENERGY]);
        } catch(ZoneComboBox.ZoneException e) {
            JOptionPane.showMessageDialog(getParent(),
              "'" + e.getMessage() + "' is not a valid zone.");
            return;
        } catch(NumberFormatException e) {
            JOptionPane.showMessageDialog(getParent(),
              "'" + e.getMessage() + "' is not a valid number.");
            return;
        }
        // Initialize counting for templates
        double[][] adTemplCost  = new double[Templates.allList.size()][CostSumCase.Template.COUNT];
        double[][] adTemplCount = new double[Templates.allList.size()][CostSumCase.Template.COUNT];
        for (int i = 0; i < Templates.allList.size(); i++) {
            for (int j = 0; j < CostSumCase.Template.COUNT; j++) {
                adTemplCost[i][j] = 0.0;
                adTemplCount[i][j] = 0.0;
            }
        }

        // Summarize every element ...
        for (int i = 0; i < Sys.elemList.size(); i++) {
            AbsElem elem = (AbsElem)Sys.elemList.elementAt(i);
            AbsElemGeom geom = elem.getElemGeomObj();

            // ... if it fits the specified zone
            if (!Zone.getZoneFits(dZone, elem))
                continue;

            // The m multiplier equals the length for lines and cables,
            // 1 otherwise
            double m = 1.0;
            if (elem instanceof Line)
                m = ((Line)elem).getLength();

            // Equipment Cost
            dData[CostSumCase.CAPITAL_COST] +=
                m * elem.getData(AbsElem.DATA_CAPITAL_COST);
            dData[CostSumCase.OM_COST] += m * elem.getData(AbsElem.DATA_OM_COST);

            // Equipment Cost per Template
            AbsElem dftTemp = elem.getDftTemp();
            if ((elem.getData(AbsElem.DATA_CAPITAL_COST) > 0.0 ||
                 elem.getData(AbsElem.DATA_OM_COST) > 0.0) &&
                dftTemp != null) {
                // Find the index of the default template
                for (int iIndex = 0; iIndex < adTemplCost.length; iIndex++){
                    if (dftTemp.equals(
                        Templates.allList.elementAt(iIndex))){
                        // Only elements with a non-zero cost contribute
                        if (elem.getData(AbsElem.DATA_CAPITAL_COST) > 0.0) {
                            adTemplCost[iIndex][CostSumCase.Template.CAPITAL] +=
                                m * elem.getData(AbsElem.DATA_CAPITAL_COST);
                            adTemplCount[iIndex][CostSumCase.Template.CAPITAL] += m;
                        }
                        if (elem.getData(AbsElem.DATA_OM_COST) > 0.0) {
                            adTemplCost[iIndex][CostSumCase.Template.OM] +=
                                m * elem.getData(AbsElem.DATA_OM_COST);
                            adTemplCount[iIndex][CostSumCase.Template.OM] += m;
                        }
                        break;
                    }
                }
            }

            // Cost of Losses
            double dVoltage, dPhases = elem.getData(AbsElem.DATA_PHASE);
            if (dPhases == 0.0)
                dPhases = 3.0;
            // WARNING: This needs to be reviewed for non-radial systems.
            if (geom.parent != null)
                dVoltage = geom.parent.getResult( AbsElem.RES_VOLT_MAG);
            else
                dVoltage = elem.getResult( AbsElem.RES_VOLT_MAG);
            // (Unit: A * A * Ohm / 1000 = A * V / 1000 = W / 1000 = kW)
            double dSeriesLosses = dPhases *
                elem.getData( AbsElem.RES_CURR_MAG) *
                elem.getData( AbsElem.RES_CURR_MAG) *
                elem.getData( AbsElem.DATA_RESISTANCE) * m / 1000;
            // (Unit: kV * kV * 1/Ohm *1000 = kV * kA *1000 = MW *1000 = kW)
            double dShuntLosses = dPhases * dVoltage * dVoltage *
                elem.getData(AbsElem.DATA_SHUNT_COND) * m * 1000 / 3;

            // (Ohmic losses are multiplied by "cost per loss and year"
            //  later in one step for better performance)
            if (elem instanceof Line) {
                if (((LineGeom)geom).getIsCable())
                    dData[CostSumCase.CABLE_LOSSES] += dSeriesLosses;
                else
                    dData[CostSumCase.LINE_LOSSES] += dSeriesLosses;
                dData[CostSumCase.OTHER_LOSSES] += dShuntLosses;
            } else if (elem instanceof Txfm || elem instanceof Regulator) {
                dData[CostSumCase.WINDING_LOSSES] += dSeriesLosses;
                dData[CostSumCase.CORE_LOSSES] += dShuntLosses;
            } else {  // other
                dData[CostSumCase.OTHER_LOSSES] += (dSeriesLosses +
                                                    dShuntLosses);

                // Cost of Poor Reliability
                if (elem instanceof Load) {
                    double dKW = elem.getData(AbsElem.DATA_CONST_POWER) *
                        elem.getData(AbsElem.DATA_PF_CONST_POWER);

                    dData[CostSumCase.MOMENT_INT_COST] +=
                        dKW * elem.getResult(AbsElem.RES_MOMENT_INT) *
                            elem.getData(AbsElem.DATA_COST_PER_INT_KW);
                    dData[CostSumCase.SUST_INT_COST] += dKW *
                          ( elem.getResult(AbsElem.RES_SUST_INT) *
                            elem.getData(AbsElem.DATA_COST_PER_INT_KW) +
                            elem.getResult(AbsElem.RES_OUT_DUR) *
                            elem.getData(AbsElem.DATA_COST_PER_INT_KWH) );
                    dData[CostSumCase.VOLT_SAG_COST] +=
                        dKW * elem.getResult(AbsElem.RES_VOLT_SAGS) * elem.getData(AbsElem.DATA_COST_PER_SAG);
                }
            }
        }

        // Multiply ohmic losses by "cost per loss and year"
        double dCostPerLoss = dData[CostSumCase.LOSS_FACTOR] *
                              dData[CostSumCase.COST_OF_ENERGY] *
                              365*24;
        dData[CostSumCase.LINE_LOSSES] *= dCostPerLoss;
        dData[CostSumCase.CABLE_LOSSES] *= dCostPerLoss;
        dData[CostSumCase.WINDING_LOSSES] *= dCostPerLoss;
        dData[CostSumCase.CORE_LOSSES] *= dCostPerLoss;
        dData[CostSumCase.OTHER_LOSSES] *= dCostPerLoss;

        // Summarize losses
        dData[CostSumCase.TOTAL_LOSSES] = dData[CostSumCase.LINE_LOSSES] +
                                          dData[CostSumCase.CABLE_LOSSES] +
                                          dData[CostSumCase.WINDING_LOSSES] +
                                          dData[CostSumCase.CORE_LOSSES] +
                                          dData[CostSumCase.OTHER_LOSSES];
        dData[CostSumCase.TOTAL_POOR_RELIA] = dData[CostSumCase.MOMENT_INT_COST] +
                                              dData[CostSumCase.SUST_INT_COST] +
                                              dData[CostSumCase.VOLT_SAG_COST];

        // Present Value
        double dAnnualCost = dData[CostSumCase.OM_COST] +
                             dData[CostSumCase.TOTAL_LOSSES] +
                             dData[CostSumCase.TOTAL_POOR_RELIA];
        dData[CostSumCase.PRESENT_VALUE] = dData[CostSumCase.CAPITAL_COST];
        if (dData[CostSumCase.DISCOUNT_RATE] == 0.0)
            dData[CostSumCase.PRESENT_VALUE] +=
                dAnnualCost * dData[CostSumCase.PROJECT_LIFE];
        else {
            double dDR = dData[CostSumCase.DISCOUNT_RATE] / 100;
            double dDRY = Math.pow(1 + dDR, dData[CostSumCase.PROJECT_LIFE]);
            dData[CostSumCase.PRESENT_VALUE] +=
                dAnnualCost * (dDRY - 1) / (dDR * dDRY);
        }

        // Format numbers and fill CostSumCase
        for (int i = CostSumCase.PRESENT_VALUE; i <= CostSumCase.TOTAL_POOR_RELIA; i++)
            c.asData[i] = curr.format(dData[i]);
        for (int i = CostSumCase.LINE_LOSSES; i < CostSumCase.COUNT; i++)
            c.asData[i] = curr.format(dData[i]);

        int iTemplCount = 0;
        // Count templates with non-zero cost
        for (int iIndex = 0; iIndex < adTemplCost.length; iIndex++)
            if (adTemplCost[iIndex][CostSumCase.Template.CAPITAL] > 0.0 ||
                adTemplCost[iIndex][CostSumCase.Template.OM] > 0.0)
                iTemplCount++;
        c.InitTemplates(iTemplCount);
        iTemplCount = 0;
        // Format templates and fill CostSumCase.Templates
        for (int iIndex = 0; iIndex < adTemplCost.length; iIndex++) {
            if (adTemplCost[iIndex][CostSumCase.Template.CAPITAL] > 0.0 ||
                adTemplCost[iIndex][CostSumCase.Template.OM] > 0.0) {
                AbsElem elem = (AbsElem)Templates.allList.elementAt(iIndex);
                int iType = elem.getType();
                boolean isLineOrCable = (iType == SysConst.TEMPLTYPE_LINE ||
                                         iType == SysConst.TEMPLTYPE_CABLE);

                CostSumCase.Template Templ = c.vTemplates[iTemplCount++];
                Templ.sName = elem.getElemGeomObj().getName();
                for (int i = 0; i < Templ.COUNT; i++) {
                    if (adTemplCost[iIndex][i] > 0.0) {
                        Templ.asCost[i] = curr.format(adTemplCost[iIndex][i]);
                        if (isLineOrCable)
                            Templ.asCount[i] =
                                numUnit.format(adTemplCount[iIndex][i]) + " " +
                                SysConst.unitStrings[Sys.prjInfo.unitOfLength];
                        else
                            Templ.asCount[i] =
                                num.format(adTemplCount[iIndex][i]) + " x";
                    } else {
                        Templ.asCost[i] = null;
                        Templ.asCount[i] = null;
                    }
                }
            }
        }

        if (bNewCase) {
            // Add case to list
            bLockShowResults = true;
            if (!Sys.costSumCases.add(c)) {
                JOptionPane.showMessageDialog(getParent(),
                    "An error occured while adding case to list.");
                return;
            }
            for (int i = 0; i < CASE_COUNT; i++)
                comboCase[i].addItem(c.asData[CostSumCase.DESCRIPTION]);
            bLockShowResults = false;

            // Select the new case in the left combo box
            comboCase[0].setSelectedIndex(Sys.costSumCases.size()-1);
            btnClearEntry.setEnabled(true);
            btnClearList.setEnabled(true);
            btnExport.setEnabled(true);
        } else
            // Just refresh results (just in case the changed case is selected)
            ShowResults();
    }

    /*
     * Sets the selected cases and displays the comparison.
     */
    private void ShowResults() {
        if (bLockShowResults)
            return;

        // Set current selected cases
        for (int i = 0; i < CASE_COUNT; i++) {
            int iIndex = comboCase[i].getSelectedIndex();
            modelCaseResult.aCases[i] = (iIndex >= 0 ?
                (CostSumCase)Sys.costSumCases.get(iIndex) : null);
        }

        // Reset template indices
        modelCaseResult.vTemplates.clear();
        // aiIndex are the indices per case handled so far
        int[] aiIndex = new int[CASE_COUNT];
        for (int i = 0; i < CASE_COUNT; i++)
            aiIndex[i] = 0;
        // For every case ...
        for (int i = 0; i < CASE_COUNT; i++) {
            if (modelCaseResult.aCases[i] != null) {
                int iEndIndex = modelCaseResult.aCases[i].vTemplates.length;
                if (aiIndex[i] < iEndIndex)
                    // ... handle the remaining templates
                    ShowResultsTemplate(aiIndex, i, iEndIndex);
            }
        }

        // Display the result
        modelCaseResult.fireTableDataChanged();
    }

    /*
     * Sets the templates of the selected results
     */
    private void ShowResultsTemplate(int[] aiIndex, int iCase, int iEndIndex) {
        CostSumCase[] aCases = modelCaseResult.aCases;
        // Hande every remaining template of this case
        while (aiIndex[iCase] < iEndIndex) {
            String sCaseName = aCases[iCase].vTemplates[aiIndex[iCase]].sName;

            // In order to display the same template in all cases of a row make sure
            // that all more right cases have either the same current template as the
            // "iCase"th case (sCasename) or it is not existing in that case.
            // If it is existing but not the current template of one case handle all
            // templates of that case up to the current template of the "iCase"th
            // case.
            // Since we assume that templates are sorted by creation date the
            // templates handled here do not exist in a more left case.
            for (int i = iCase+1; i < CASE_COUNT; i++) {
                if (aCases[i] != null &&
                    aiIndex[i] < aCases[i].vTemplates.length &&
                    !sCaseName.equals(aCases[i].vTemplates[aiIndex[i]].sName)) {
                    for (int j = aiIndex[i]+1; j < aCases[i].vTemplates.length; j++) {
                        if (sCaseName.equals(aCases[i].vTemplates[j].sName)) {
                            ShowResultsTemplate(aiIndex, i, j);
                            break;
                        }
                    }
                }
            }

            // Set the indices of the current row or -1 if the template does not exist
            // in that case. aiRow[CASE_COUNT] is used for determing the template name
            // of the current row.
            int[] aiRow = new int[CASE_COUNT+1];
            aiRow[CASE_COUNT] = iCase;
            for (int i = 0; i < CASE_COUNT; i++) {
                if (i >= iCase &&
                    aCases[i] != null &&
                    aiIndex[i] < aCases[i].vTemplates.length &&
                    sCaseName.equals(aCases[i].vTemplates[aiIndex[i]].sName))
                    aiRow[i] = aiIndex[i]++;
                else
                    aiRow[i] = -1;
            }
            modelCaseResult.vTemplates.add(aiRow);
        }
    }

    /*
     * Export the results to Excel
     */
    private void ExportResults() {
        try {
            File f = Sys.personalInfo.createTempExportFile();
            BufferedWriter bw = new BufferedWriter(new FileWriter(f));
            bw.write("\"Cost Summary\"");
            bw.newLine();
            bw.newLine();
            bw.newLine();
            for (int i = 0; i < modelCaseResult.getRowCount(); i++) {
                Object value = modelCaseResult.getValueAt(i, 0);
                bw.write("\"" + (value != null ? value : "") + "\"");
                for (int j = 1; j < modelCaseResult.getColumnCount(); j++) {
                    value = modelCaseResult.getValueAt(i, j);
                    bw.write(",\"" + (value != null ? value : "") + "\"");
                }
                bw.newLine();
            }
            bw.close();

            Sys.personalInfo.execExcel(f.getPath());
        } catch(IOException e) {
            JOptionPane.showMessageDialog(getParent(),
                "Error occured when writing to file:\n" + e.getMessage());
        } catch(Exception e) {
            JOptionPane.showMessageDialog(getParent(), e);
        }
    }

    /* CASE_COUNT constant */
    private static final int CASE_COUNT = 2;

    /* Dialog items */
    private JTextField tfDescription;
    private ZoneComboBox comboArea;
    private JTextField tfDiscountRate;
    private JTextField tfProjectLife;
    private JTextField tfCostOfEnergy;
    private JTextField tfLossFactor;
    private JButton btnAddToList;
    private JComboBox[] comboCase = new JComboBox[CASE_COUNT];
    private CaseResultModel modelCaseResult;
    private JTable tblCaseResult;
    private JButton btnClearEntry;
    private JButton btnClearList;
    private JButton btnExport;

    private boolean bLockShowResults = false;

    /*
     * Table model for case results
     */
    private class CaseResultModel extends AbstractTableModel {
        public CostSumCase[] aCases = new CostSumCase[CASE_COUNT];
        public Vector vTemplates = new Vector(50, 10); // of int[CASE_COUNT+1]

        public int getColumnCount() {
            return 1 + 2*CASE_COUNT;
        }

        public String getColumnName(int column) {
            return "";
        }

        public Class getColumnClass(int column) {
            if (column == 0)            // Name column
                return Object.class;
            else                        // Data columns
                return Number.class;
        }

        public int getRowCount() {
            return asBasic.length +
                CostSumCase.Template.COUNT * (2+vTemplates.size());
        }

        public Object getValueAt(int row, int col) {
            int iCaseNo = (col-1) / 2;
            int iCaseCol = (col-1) % 2;
            // The table is divided into two areas, the basic area and the template
            // area. The basic area consists of a known count of basic and summarized
            // information. The template area is again divided into two (similar)
            // parts, the "Equipment Capital Cost" and "Equipment O&M Cost". Each part
            // consists of a two line header and a runtime-determined count of
            // template rows.
            if (row < asBasic.length) {
                // Basic area
                if (col == 0)                       // Name column
                    return asBasic[row];
                else if (aCases[iCaseNo] == null || // (no case to display)
                         aiBasic[row] < 0 ||        // (no data to display)
                         iCaseCol == 0)             // (this column is only used for
                    return null;                    //  templates)
                else // (iCaseCol == 1)             // Data column
                    return aCases[iCaseNo].asData[aiBasic[row]];
            } else {
                // Template area
                int iCostNo = (row - asBasic.length) / (2 + vTemplates.size());
                int iTemplRow = (row - asBasic.length) % (2 + vTemplates.size()) - 2;
                if (iTemplRow < 0) {
                    // Header rows
                    if (iTemplRow == -1 && col == 0)
                        return asTemplate[iCostNo];
                    else
                        return null;
                } else {
                    // Data rows
                    int[] aiData = (int[])vTemplates.get(iTemplRow);

                    if (col == 0)                        // Name column
                        return aCases[aiData[CASE_COUNT]].
                             vTemplates[aiData[aiData[CASE_COUNT]]].sName;
                    else if (aCases[iCaseNo] == null ||  // (no case to display)
                             aiData[iCaseNo] < 0)        // (no data to display)
                        return null;
                    else if (iCaseCol == 0)              // Count Column
                        return aCases[iCaseNo].vTemplates[aiData[iCaseNo]].
                            asCount[iCostNo];
                    else // (iCaseCol == 1)              // Cost column
                        return aCases[iCaseNo].vTemplates[aiData[iCaseNo]].
                            asCost[iCostNo];
                }
            }
        }

        private final String[] asBasic = new String[] {
            "Description",
            null,
            "Present Value [$]",
            "Capital Cost [$]",
            "O&M Cost [$/yr]",
            "Cost of Ohmic Losses [$/yr]",
            "Cost of Poor Reliability [$/yr]",
            null,
            "Area",
            "Discount Rate [%]",
            "Project Life [yr]",
            "Loss Factor",
            "Cost of Energy [$/kWh]",
            null,
            "Cost of Ohmic Losses",
            "  Line Losses [$/yr]",
            "  Cable Losses [$/yr]",
            "  Winding Losses [$/yr]",
            "  Core Losses [$/yr]",
            "  Other Losses [$/yr]",
            null,
            "Cost of Poor Reliability",
            "  Momentary Interruptions [$/yr]",
            "  Sustained Interruptions [$/yr]",
            "  Critical Voltage Sags [$/yr]"
        };
        private final int[] aiBasic = new int[] {
            // Name,                        field number
            /* Description */               CostSumCase.DESCRIPTION,
            /* --- */                       -1,
            /* Present Value */             CostSumCase.PRESENT_VALUE,
            /* Capital Cost */              CostSumCase.CAPITAL_COST,
            /* O&M Cost */                  CostSumCase.OM_COST,
            /* Cost of Ohmic Losses */      CostSumCase.TOTAL_LOSSES,
            /* Cost of Poor Reliability */  CostSumCase.TOTAL_POOR_RELIA,
            /* --- */                       -1,
            /* Zone */                      CostSumCase.ZONE,
            /* Discount Rate */             CostSumCase.DISCOUNT_RATE,
            /* Project Life */              CostSumCase.PROJECT_LIFE,
            /* Loss Factor */               CostSumCase.LOSS_FACTOR,
            /* Cost of Energy */            CostSumCase.COST_OF_ENERGY,
            /* --- */                       -1,
            /* Cost of Ohmic Losses */      -1,
            /*   Line Losses */             CostSumCase.LINE_LOSSES,
            /*   Cable Losses */            CostSumCase.CABLE_LOSSES,
            /*   Winding Losses */          CostSumCase.WINDING_LOSSES,
            /*   Core Losses */             CostSumCase.CORE_LOSSES,
            /*   Other Losses */            CostSumCase.OTHER_LOSSES,
            /* --- */                       -1,
            /* Cost of Poor Reliability */  -1,
            /*   Momentary Interruptions */ CostSumCase.MOMENT_INT_COST,
            /*   Sustained Interruptions */ CostSumCase.SUST_INT_COST,
            /*   Critical Voltage Sags */   CostSumCase.VOLT_SAG_COST
        };
        private final String[] asTemplate = new String[] {
            "Equipment Capital Cost [$]",
            "Equipment O&M Cost [$/yr]"
        };
    };
}
